/**
 * 
 */
package qy.jalgotrade.optimizer.worker;

import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.commons.math3.stat.descriptive.summary.Sum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import qy.jalgotrade.barfeed.BaseBarFeed;
import qy.jalgotrade.optimizer.base.OptimizingGoal;
import qy.jalgotrade.optimizer.base.Parameters;
import qy.jalgotrade.optimizer.base.Results;
import qy.jalgotrade.stratanalyzer.DrawDown;
import qy.jalgotrade.stratanalyzer.SharpeRatio;
import qy.jalgotrade.stratanalyzer.Trades;
import qy.jalgotrade.stratanalyzer.returns.Returns;
import qy.jalgotrade.strategy.BacktestingStrategy;
import qy.jalgotrade.utils.CommonUtils;

/**
 * @author c-geo
 *
 */
public final class RemoteWorker {

	/**
	 * 
	 * @author c-geo
	 *
	 */
	private static class RemoteWorkerRunnable extends AbstractWorker {

		/**
		 * 
		 */
		private Class<? extends BacktestingStrategy> __strategyClass;

		/**
		 * 
		 * @param strategyClass
		 * @param address
		 * @param port
		 * @param goal
		 * @param workerName
		 * @throws UnknownHostException
		 * @throws MalformedURLException
		 */
		public RemoteWorkerRunnable(Class<? extends BacktestingStrategy> strategyClass, String address, int port,
		        OptimizingGoal goal, String workerName) throws UnknownHostException, MalformedURLException {

			super(address, port, goal, workerName);
			__strategyClass = strategyClass;
		}

		/**
		 * 
		 */
		@Override
		public Results runStrategy(BaseBarFeed feed, Parameters parameters) {

			Class<?>[] argTypes = new Class[parameters.getKwargs().size() + 1];
			Object[] args = new Object[parameters.getKwargs().size() + 1];
			argTypes[0] = BaseBarFeed.class;
			args[0] = feed;

			int i = 1;
			for (Map.Entry<String, Object> kvp : parameters.getKwargs().entrySet()) {
				argTypes[i] = kvp.getValue().getClass();
				args[i] = kvp.getValue();
				i++;
			}

			BacktestingStrategy strat;
			OptimizingGoal goal = getGoal();
			SharpeRatio sharpe = null;
			Returns returns = null;
			Trades trades = null;
			DrawDown drawdown = null;
			try {
				strat = ConstructorUtils.invokeConstructor(__strategyClass, args);
				if (goal != null && goal.getGoalKpi() != Results.KPI.RESULT) {
					sharpe = new SharpeRatio();
					returns = new Returns();
					trades = new Trades();
					drawdown = new DrawDown();
					strat.attachAnalyzerEx(sharpe, "sharpe");
					strat.attachAnalyzerEx(returns, "returns");
					strat.attachAnalyzerEx(drawdown, "drawdown");
					strat.attachAnalyzerEx(trades, "trades");
				}
				strat.run();

			} catch (Exception e) {

				getLogger().error("Exception: {} (stack trace: {})", e, ExceptionUtils.getStackTrace(e));
				return null;
			}

			if (goal == null || goal.getGoalKpi() == Results.KPI.RESULT) {

				return new Results(parameters, strat.getResult());
			} else {

				Sum sum = new Sum();
				Double[] values = new Double[trades.getCommissionsForAllTrades().size()];
				double allCommissions = sum
				        .evaluate(ArrayUtils.toPrimitive(trades.getCommissionsForAllTrades().toArray(values)));

				return new Results(parameters, strat.getResult(), returns.getCumulativeReturns().__getitem__(-1),
				        sharpe.getSharpeRatio(0.035), drawdown.getMaxDrawDown(), drawdown.getLongestDrawDownDuration(),
				        feed.getTotalTradeDays(), trades.getCount(), trades.getProfitableCount(), trades.getUnprofitableCount(),
				        trades.getEvenCount(),
				        trades.getCount() != 0 ? ((double) trades.getProfitableCount()) / trades.getCount()
				                : Double.NaN,
				        allCommissions);
			}
		}
	}

	private static Logger logger = LoggerFactory.getLogger(RemoteWorker.class);

	/**
	 * Executes one or more worker processes that will run a strategy with the bars and parameters
	 * supplied by the server.
	 * 
	 * @param strategyClass The strategy class.
	 * @param address       The address of the server.
	 * @param port          The port where the server is listening for incoming connections.
	 * @throws UnknownHostException
	 * @throws MalformedURLException
	 */
	public static void run(Class<? extends BacktestingStrategy> strategyClass, String address, int port)
	        throws UnknownHostException, MalformedURLException {

		run(strategyClass, address, port, new OptimizingGoal(), 0, "");
	}

	/**
	 * Executes one or more worker processes that will run a strategy with the bars and parameters
	 * supplied by the server.
	 * 
	 * @param strategyClass The strategy class.
	 * @param address       The address of the server.
	 * @param port          The port where the server is listening for incoming connections.
	 * @param goal
	 * @throws MalformedURLException
	 * @throws UnknownHostException
	 */
	public static void run(Class<? extends BacktestingStrategy> strategyClass, String address, int port,
	        OptimizingGoal goal) throws UnknownHostException, MalformedURLException {

		run(strategyClass, address, port, goal, 0, "");
	}

	/**
	 * Executes one or more worker processes that will run a strategy with the bars and parameters
	 * supplied by the server.
	 * 
	 * @param strategyClass The strategy class.
	 * @param address       The address of the server.
	 * @param port          The port where the server is listening for incoming connections.
	 * @param goal
	 * @param workerCount   The number of worker processes to run. If None then as many workers as CPUs
	 *                      are used.
	 * @throws MalformedURLException
	 * @throws UnknownHostException
	 */
	public static void run(Class<? extends BacktestingStrategy> strategyClass, String address, int port,
	        OptimizingGoal goal, int workerCount) throws UnknownHostException, MalformedURLException {

		run(strategyClass, address, port, goal, workerCount, "");
	}

	/**
	 * Executes one or more worker processes that will run a strategy with the bars and parameters
	 * supplied by the server.
	 * 
	 * @param strategyClass The strategy class.
	 * @param address       The address of the server.
	 * @param port          The port where the server is listening for incoming connections.
	 * @param goal
	 * @param workerCount   The number of worker processes to run. If None then as many workers as CPUs
	 *                      are used.
	 * @param workerName    A name for the worker. A name that identifies the worker. If None, the
	 *                      hostname is used.
	 * @throws MalformedURLException
	 * @throws UnknownHostException
	 */
	public static void run(Class<? extends BacktestingStrategy> strategyClass, String address, int port,
	        OptimizingGoal goal, int workerCount, String workerName)
	        throws UnknownHostException, MalformedURLException {

		assert workerCount >= 0;
		if (workerCount == 0) {
			workerCount = Integer.parseInt(System.getenv(CommonUtils.NUMBER_OF_PROCESSORS));
		}

		List<Thread> workers = new ArrayList<>();
		// Build the worker processes.
		for (int i = 0; i < workerCount; i++) {
			// Create a worker and run it.
			RemoteWorkerRunnable rwr = new RemoteWorkerRunnable(strategyClass, address, port, goal, workerName);
			Thread workerThread = new Thread(rwr);
			workers.add(workerThread);
		}

		// Start workers
		for (Thread e : workers) {
			e.start();
		}

		// Wait workers
		for (Thread e : workers) {
			try {
				e.join();
			} catch (InterruptedException ex) {
				logger.warn("InterruptedException: {} (stack trace: {})", ex, ExceptionUtils.getStackTrace(ex));
			}
		}
	}

	/**
	 * 
	 */
	private RemoteWorker() {

	}
}
